const resourceDao = require("@/dao/resource");
const z_base = require("@/dao/z_base");
const ResourceModel = require("@/model/Resource");
const RDBC = require("@/model/RDBC");
const {
  DeletePermissionForRole,
  AddPermissionForRole,
  UpdatePolicy,
  DeletePermission,
} = require("@/utils/casbin");
const { ErrorCode } = require("@/utils/r/code");

const resource = {
  async GetTreeList(query) {
    // 根据资源关键字查询资源列表 非树形
    const resources = await resourceDao.GetListByKeyword(query?.keyword); //关键字可能没

    // []resource -> []resourceVo
    return resources2ResourceVos(resources);
  },
  // 编辑资源的匿名访问, 关联更新 casbin_rule 中数据
  async UpdateAnonymous(body) {
    const existById = await z_base.GetOne(ResourceModel.Resource, {
      id: body.id,
    });
    if (!existById.id) {
      //更新匿名数据，查不到
      return ErrorCode.ERROR_RESOURCE_NOT_EXIST;
    }
    await z_base.UpdatesMap(
      ResourceModel.Resource,
      { is_anonymous: +body.is_anonymous },
      { id: body.id }
    );

    if (+body.is_anonymous === 0) {
      // 删除
      await DeletePermissionForRole("anonymous", body.url, body.request_method);
    } else {
      //新增
      await AddPermissionForRole("anonymous", body.url, body.request_method);
    }

    return ErrorCode.OK;
  },
  /* 新增或编辑资源, 关联更新 casbin_rule 中数据 */
  async SaveOrUpdate(body) {
    // 检查资源是否存在 修改携带id
    const existByName = await z_base.GetOne(ResourceModel.Resource, {
      name: body.name,
    });
    //更新的时候走该判断
    if (existByName && +existByName?.id !== +body.id) {
      return ErrorCode.ERROR_RESOURCE_NAME_EXIST;
    }
    body.updated_at = new Date().toLocaleString();
    if (body.id) {
      //更新
      const oldResource = await z_base.GetOne(ResourceModel.Resource, {
        id: body.id,
      });
      await z_base.UpdatesMap(ResourceModel.Resource, body, { id: body.id });
      //更新关联中的值 casbin_rule
      await UpdatePolicy(
        [oldResource.url, oldResource.request_method],
        [body.url, body.request_method]
      ); //旧值和新值
    } else {
      body.created_at = new Date().toLocaleString();
      //新增
      await z_base.Create(ResourceModel.Resource, body);
      // * 解决前端的 BUG: 级联选中某个父节点后, 新增的子节点默认会展示被选中, 实际上未被选中值
      // * 解决方案: 新增子节点后, 删除该节点对应的父节点与角色的关联关系
      await z_base.Delete(RDBC.RoleResource, { resource_id: body.parent_id });
    }
    return ErrorCode.OK;
  },
  async Delete(resource_id) {
    // 删除资源, 关联删除 casbin_rule 中数据
    // TODO: 考虑删除模块后, 其子资源怎么办? 目前做法是有子资源无法删除

    //检查删除的资源是否存在
    const existResourceById = await z_base.GetOne(ResourceModel.Resource, {
      id: resource_id,
    });
    if (!existResourceById?.id) {
      return ErrorCode.ERROR_RESOURCE_NOT_EXIST;
    }

    //如果是删除模块，则需要判断当前模块下是否存在接口资源，否则删除模块后，资源接口还会存在
    const existResourceByChild = await z_base.GetOne(ResourceModel.Resource, {
      parent_id: resource_id,
    });
    if (existResourceByChild) {
      return ErrorCode.ERROR_RESOURCE_USED_BY_ROLE;
    }

    //检查该资源下是否被分配权限使用
    const existRoleResource = await z_base.GetOne(RDBC.RoleResource, {
      resource_id: resource_id,
    });
    if (existRoleResource?.resource_id) {
      return ErrorCode.ERROR_RESOURCE_USED_BY_ROLE;
    }
    //删除资源
    await z_base.Delete(ResourceModel.Resource, { id: resource_id });

    //删除casbin_rule中的数据
    if (
      existResourceById.url !== "" &&
      existResourceById.request_method !== ""
    ) {
      // ! 关联删除 casbin_rule 中的数据
      // 因为前面检查过 role_resource 下是否有数据, 理论上来说不会真正到关联删除这步
      await DeletePermission(
        existResourceById.url,
        existResourceById.request_method
      );
    }

    return ErrorCode.OK;
  },
  async GetOptionList() {
    let resList = [];
    //获取非禁用的资源
    let [resources] = await global.mysqlPool.execute(
      `select id,name,parent_id from resource where is_anonymous = ?`,
      [0]
    );
    //获取一级目录，parent_id = 0
    let parentList = getModuleList(resources);
    //获取二级目录
    let childrenMap = getChildrenMap(resources);

    for (let item of parentList) {
      //构建children
      /* 
        {
          id,
          label:
          children:[]
        }
      */
      let childrenOptionVos = [];
      if (childrenMap[item.id]) {
        for (let re of childrenMap[item.id]) {
          childrenOptionVos.push({
            key: re.id,
            title: re.name,
            children: null,
          });
        }
      }
      resList.push({
        key: item.id,
        title: item.name,
        children: childrenOptionVos,
      });
    }
    return resList;
  },
};

async function resources2ResourceVos(resources) {
  let resList = [];
  // 找出父节点列表 (parentId == 0)
  let parentList = getModuleList(resources);

  // 存储每个节点对应[子资源列表]的 map
  let childrenMap = getChildrenMap(resources);

  for (let item of parentList) {
    let resourceVo = resource2ResourceVo(item);
    resourceVo.children = [];
    if (childrenMap[item.id]) {
      //防止新建的父级没有自己出错
      for (let child of childrenMap[item.id]) {
        resourceVo.children.push(resource2ResourceVo(child));
      }
    }

    resList.push(resourceVo);
  }
  return resList;
}

function resource2ResourceVo(r) {
  return {
    id: r.id,
    name: r.name,
    url: r.url,
    request_method: r.request_method,
    is_anonymous: r.is_anonymous,
    created_at: r.created_at,
  };
}

// 获取一级资源 (没有 parent_id 的 resource)
function getModuleList(resources) {
  let parentList = [];
  for (let r of resources) {
    if (r.parent_id === 0) {
      parentList.push(r);
    }
  }
  return parentList;
}
// 存储每个节点对应[子资源列表]的 map: key 为 resourceId, value 为其对应的 childrenList
function getChildrenMap(resources) {
  let childrenMap = {};
  for (let r of resources) {
    if (r.parent_id !== 0) {
      if (!childrenMap[r.parent_id]) {
        childrenMap[r.parent_id] = [];
      }
      childrenMap[r.parent_id].push(r);
    }
  }

  return childrenMap;
}

module.exports = resource;
